<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
// namespace spica\core\validator\pattern\finance;

include_once 'library/spica/core/validator/Common.php';

/**
 * A validator that ensures that a string is valid for credit card numbers.
 * Its evaluation rules confirm that the text is all digits, follows Luhn's formula,
 * and if desired, matches specific brands of credit cards. It does not provide
 * credit card number approval software.
 *
 * @category   spica
 * @package    core
 * @subpackage validator\pattern\finance
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      March 30, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Finance.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaCreditCardNumberValidator extends SpicaValidator
{
    /**
     * American Express.
     *
     * @var string
     */
    const AMERICAN = '1';

    /**
     * Diner's Club.
     *
     * @var string
     */
    const DINERS  = '2';

    /**
     * Discover Card.
     *
     * @var string
     */
    const DISCOVER = '3';

    /**
     * Mastercard.
     *
     * @var string
     */
    const MASTER   = '4';

    /**
     * Visa Card.
     *
     * @var string
     */
    const VISA     = '5';

    /**
     * JCB
     *
     * @var string
     */
    const JCB = '6';

    /**
     * AMEX.
     *
     * @var string
     */
    const AMEX = '7';

    /**
     * Australian BankCard
     *
     * @var string
     */
    const AUSTRALIAN_BANKCARD = '8';

    /**
     * Enroute
     *
     * @var string
     */
    const ENROUTE = '9';

    /**
     * An all electronic debit card payment system. Founded in 1988,
     * now over 20 million card holders and more than 480000 outlets
     *
     * @var string
     */
    const SWITCH_CARD = '10';

    /**
     * List of credit card number patterns.
     *
     * @var array
     */
    protected $_patterns = array(
    1  => '/^([34|37]{2})([0-9]{13})$/',
    2  => '/^([30|36|38]{2})([0-9]{12})$/',
    3  => '/^([6011]{4})([0-9]{12})$/',
    4  => '/^([51|52|53|54|55]{2})([0-9]{14})$/',
    5  => '/^([4]{1})([0-9]{12,15})$/',
    6  => '/^[3088|3096|3112|3158|3337|3528]\d{12}$/',
    7  => '/^3[4,7]\d{13}$/',
    8  => '/^5610-?\d{4}-?\d{4}-?\d{4}$/',
    9  => '/^[2014|2149]\d{11}$/',
    10 => '/^[4903|4911|4936|5641|6333|6759|6334|6767]\d{12}$/',
    );

    /**
     * Matching pattern in use.
     *
     * @var string
     */
    protected $_pattern;

    /**
     * Card type to check with.
     *
     * @var int
     */
    protected $_cardType;

    /**
     * Creates a validator with the provided violation message.
     *
     * @param string $violationMessage violation message to use if the validation fails
     * @param string $cardType Credit card type
     * @param bool   $required Determines whether this value is required. Defaults to true
     */
    public function __construct($violationMessage, $cardType, $required = true)
    {
        if (false === ctype_digit($cardType))
        {
            throw new InvalidArgumentException('The argument $cardType must be an integer.');
        }

        if (false === isset($this->_patterns[$cardType]))
        {
            throw new InvalidArgumentException('$cardType numberd '.$cardType.' is not supported.');
        }

        $this->_pattern          = $this->_patterns[$cardType];
        $this->_cardType         = (int) $cardType;
        $this->_violationMessage = $violationMessage;
        $this->_required         = (bool) $required;
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/validator/SpicaValidator#isValid()
     */
    public function isValid($value = null)
    {
        $this->_value = trim($value);

        if (true === spica_valid_empty_string($value))
        {
            return false === $this->_required;
        }

        if (true === $this->_checkFormat($value) && true === $this->_checkSum($value))
        {
            return true;
        }

        return false;
    }

    /**
     * Checks number format.
     *
     * @return bool
     */
    protected function _checkFormat($value)
    {
        return (bool) preg_match($this->_pattern, $value);
    }

    /**
     * Validates the card number with the Luhn algorithm (Mod 10).
     *
     * @param  string $number
     * @return bool
     */
    protected function _checkSum($number)
    {
        $doubleThisDigit = false;
        $sum      = 0;
        $loopTime = strlen($number) - 1;

        for ($i   = $loopTime; $i >= 0; $i--)
        {
            if ($doubleThisDigit)
            {
                $digit = $number[$i] * 2;
                // subtract 9 if the doubled digit is > 9
                $digit -= ($digit > 9) ? 9 : 0;
            }
            else
            {
                // the other digits get summed without doubling
                $digit = $number[$i];
            }

            $sum += $digit;
            $doubleThisDigit = !$doubleThisDigit; // flip flop
        }

        // the check is valid if the sum is a multiple of 10
        return ($sum % 10 == 0);
    }
}

/**
 * A validator that ensures that a string is valid for SWIFT and BIC Codes.
 *
 * Example: UBSWCHZH80A.
 * + UBSW is the bank code consisting of 4 Alphabetic Symbols.
 * + CH is the country, in this case Switzerland, made up out of 2 Alphabetic Symbols.
 * + ZH is the area code, in this case Zürich. This part of the code consists of two
 *   alphanumeric symbols. If the second symbol is a 1, the participant is passive.
 *   If it is a 0 we are dealing with a test-BIC.
 * + 80A is the 3 symbol branch code, which also consists of alphanumeric symbols and is optional.
 *
 * @see        http://en.wikipedia.org/wiki/ISO_9362
 *
 * @category   spica
 * @package    core
 * @subpackage validator\pattern\finance
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      July 11, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Finance.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaSwiftBicValidator extends SpicaValidator
{
    /**
     * Creates a validator with the provided violation message.
     *
     * @param string $violationMessage violation message to use if the validation fails
     * @param bool   $required Determines whether this value is required. Defaults to true
     */
    public function __construct($violationMessage, $required = true)
    {
        $this->_violationMessage = $violationMessage;
        $this->_required         = (bool) $required;
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/validator/SpicaValidator#isValid()
     */
    public function isValid($value = null)
    {
        $this->_value = $value;

        // No data is provided but this value is optional (not mandatory)
        if (true === spica_valid_empty_string($value))
        {
            return (false === $this->_required);
        }

        return (bool) preg_match('/^([a-zA-Z]){4}([a-zA-Z]){2}([0-9a-zA-Z]){2}([0-9a-zA-Z]{3})?$/', $value);
    }
}

?>